--! This file is part of the Wupper firmware distribution (https://gitlab.nikhef.nl/franss/wupper/).
--! Copyright (C) 2001-2021 CERN for the benefit of the ATLAS collaboration.
--! Authors:
--!               Frans Schreuder
--!               Israel Grayzman
--!
--!   Licensed under the Apache License, Version 2.0 (the "License");
--!   you may not use this file except in compliance with the License.
--!   You may obtain a copy of the License at
--!
--!       http://www.apache.org/licenses/LICENSE-2.0
--!
--!   Unless required by applicable law or agreed to in writing, software
--!   distributed under the License is distributed on an "AS IS" BASIS,
--!   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--!   See the License for the specific language governing permissions and
--!   limitations under the License.

--
-- Simple I2C controller
--
-- 1) No multimaster
-- 2) No slave mode
-- 3) No fifo's
--
-- notes:
-- Every command is acknowledged. Do not set a new command before previous is acknowledged.
-- Dout is available 1 clock cycle later as cmd_ack
--

library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use ieee.numeric_std_unsigned.all;

package I2C is
    component simple_i2c is
        port (
            clk : in std_logic;
            ena : in std_logic;
            nReset : in std_logic;

            clk_cnt : in unsigned(7 downto 0);  -- 4x SCL

            -- input signals
            start,
            stop,
            read,
            write,
            ack_in : std_logic;
            Din : in std_logic_vector(7 downto 0);

            -- output signals
            cmd_ack : out std_logic;
            ack_out : out std_logic;
            Dout : out std_logic_vector(7 downto 0);

            -- i2c signals
            SCL_i : in std_logic;
            SDA_i : in std_logic;
            SCL_o : out std_logic;
            SDA_o : out std_logic
        );
    end component simple_i2c;
end package I2C;


library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use ieee.numeric_std_unsigned.all;

entity simple_i2c is
    port (
        clk : in std_logic;
        ena : in std_logic;
        nReset : in std_logic;

        clk_cnt : in unsigned(7 downto 0);  -- 4x SCL

        -- input signals
        start,
        stop,
        read,
        write,
        ack_in : std_logic;
        Din : in std_logic_vector(7 downto 0);

        -- output signals
        cmd_ack : out std_logic;
        ack_out : out std_logic;
        Dout : out std_logic_vector(7 downto 0);

        -- i2c signals
        SCL_i : in std_logic;
        SDA_i : in std_logic;
        SCL_o : out std_logic;
        SDA_o : out std_logic
    );
end entity simple_i2c;

architecture structural of simple_i2c is
    component i2c_core is
        port (
            clk : in std_logic;
            nReset : in std_logic;

            clk_cnt : in unsigned(7 downto 0);

            cmd : in std_logic_vector(2 downto 0);
            cmd_ack : out std_logic;
            busy : out std_logic;

            Din : in std_logic;
            Dout : out std_logic;

            SCL_i : in std_logic;
            SDA_i : in std_logic;
            SCL_o : out std_logic;
            SDA_o : out std_logic
        );
    end component i2c_core;

    -- commands for i2c_core
    constant CMD_NOP  : std_logic_vector(2 downto 0) := "000";
    constant CMD_START  : std_logic_vector(2 downto 0) := "010";
    constant CMD_STOP  : std_logic_vector(2 downto 0) := "011";
    constant CMD_READ  : std_logic_vector(2 downto 0) := "100";
    constant CMD_WRITE  : std_logic_vector(2 downto 0) := "101";

    -- signals for i2c_core
    signal core_cmd : std_logic_vector(2 downto 0);
    signal core_ack, core_rxd, core_txd : std_logic;

    -- signals for shift register
    signal sr : std_logic_vector(7 downto 0); -- 8bit shift register
    signal shift, ld : std_logic;

    -- signals for state machine
    signal go, host_ack : std_logic;
begin
    -- hookup i2c core
    u1: i2c_core port map(
            clk => clk,
            nReset => nReset,
            clk_cnt => clk_cnt,
            cmd => core_cmd,
            cmd_ack => core_ack,
            busy => open,
            Din => core_txd,
            Dout => core_rxd,
            SCL_i => SCL_i,
            SDA_i => SDA_i,
            SCL_o => SCL_o,
            SDA_o => SDA_o
        );
    -- generate host-command-acknowledge
    cmd_ack <= host_ack;

    -- generate go-signal
    go <= (read or write) and not host_ack;

    -- assign Dout output to shift-register
    Dout <= sr;

    -- assign ack_out output to core_rxd (contains last received bit)
    ack_out <= core_rxd;

    -- generate shift register
    shift_register: process(clk)
    begin
        if (clk'event and clk = '1') then
            if (ld = '1') then
                sr <= Din;
            elsif (shift = '1') then
                sr <= (sr(6 downto 0) & core_rxd);
            end if;
        end if;
    end process shift_register;

    --
    -- state machine
    --
    statemachine : block
        type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop);
        signal state : states;
        signal dcnt : unsigned(2 downto 0);
    begin
        --
        -- command interpreter, translate complex commands into simpler I2C commands
        --
        nxt_state_decoder: process(clk, nReset, state, dcnt, core_txd, core_cmd, go, start, stop, read, core_ack, sr, ack_in)
            variable nxt_state : states;
            variable idcnt : unsigned(2 downto 0);
            variable ihost_ack : std_logic;
            variable icore_cmd : std_logic_vector(2 downto 0);
            variable icore_txd : std_logic;
            variable ishift, iload : std_logic;
        begin
            -- 8 databits (1byte) of data to shift-in/out
            idcnt := dcnt;

            -- no acknowledge (until command complete)
            ihost_ack := '0';

            icore_txd := core_txd;

            -- keep current command to i2c_core
            icore_cmd := core_cmd;

            -- no shifting or loading of shift-register
            ishift := '0';
            iload := '0';

            -- keep current state;
            nxt_state := state;
            case state is
                when st_idle =>
                    if (go = '1') then
                        if (start = '1') then
                            nxt_state := st_start;
                            icore_cmd := CMD_START;
                        elsif (read = '1') then
                            nxt_state := st_read;
                            icore_cmd := CMD_READ;
                            idcnt := "111";
                        else
                            nxt_state := st_write;
                            icore_cmd := CMD_WRITE;
                            idcnt := "111";
                            iload := '1';
                        end if;
                    end if;

                when st_start =>
                    if (core_ack = '1') then
                        if (read = '1') then
                            nxt_state := st_read;
                            icore_cmd := CMD_READ;
                            idcnt := "111";
                        else
                            nxt_state := st_write;
                            icore_cmd := CMD_WRITE;
                            idcnt := "111";
                            iload := '1';
                        end if;
                    end if;

                when st_write =>
                    if (core_ack = '1') then
                        idcnt := dcnt -1;  -- count down Data_counter
                        icore_txd := sr(7);
                        if (dcnt = 0) then
                            nxt_state := st_ack;
                            icore_cmd := CMD_READ;
                        else
                            ishift := '1';
                        --              icore_txd := sr(7);
                        end if;
                    end if;

                when st_read =>
                    if (core_ack = '1') then
                        idcnt := dcnt -1;  -- count down Data_counter
                        ishift := '1';
                        if (dcnt = 0) then
                            nxt_state := st_ack;
                            icore_cmd := CMD_WRITE;
                            icore_txd := ack_in;
                        end if;
                    end if;

                when st_ack =>
                    if (core_ack = '1') then
                        -- generate command acknowledge signal
                        ihost_ack := '1';

                        -- Perform an additional shift, needed for 'read' (store last received bit in shift register)
                        ishift := '1';

                        -- check for stop; Should a STOP command be generated ?
                        if (stop = '1') then
                            nxt_state := st_stop;
                            icore_cmd := CMD_STOP;
                        else
                            nxt_state := st_idle;
                            icore_cmd := CMD_NOP;
                        end if;
                    end if;

                when st_stop =>
                    if (core_ack = '1') then
                        nxt_state := st_idle;
                        icore_cmd := CMD_NOP;
                    end if;

                when others => -- illegal states -- @suppress "Case statement contains all choices explicitly. You can safely remove the redundant 'others'"
                    nxt_state := st_idle;
                    icore_cmd := CMD_NOP;
            end case;

            -- generate registers
            if (nReset = '0') then
                core_cmd <= CMD_NOP;
                core_txd <= '0';

                shift <= '0';
                ld <= '0';

                dcnt <= "111";
                host_ack <= '0';

                state <= st_idle;
            elsif (clk'event and clk = '1') then
                if (ena = '1') then
                    state <= nxt_state;

                    dcnt <= idcnt;
                    shift <= ishift;
                    ld <= iload;

                    core_cmd <= icore_cmd;
                    core_txd <= icore_txd;

                    host_ack <= ihost_ack;
                end if;
            end if;
        end process nxt_state_decoder;

    end block statemachine;

end architecture structural;


--
--
-- I2C Core
--
-- Translate simple commands into SCL/SDA transitions
-- Each command has 5 states, A/B/C/D/idle
--
-- start:  SCL  ~~~~~~~~~~\____
--  SDA  ~~~~~~~~\______
--     x | A | B | C | D | i
--
-- repstart  SCL  ____/~~~~\___
--  SDA  __/~~~\______
--     x | A | B | C | D | i
--
-- stop  SCL  ____/~~~~~~~~
--  SDA  ==\____/~~~~~
--     x | A | B | C | D | i
--
--- write  SCL  ____/~~~~\____
--  SDA  ==X=========X=
--     x | A | B | C | D | i
--
--- read  SCL  ____/~~~~\____
--  SDA  XXXX=====XXXX
--     x | A | B | C | D | i
--

-- Timing:    Normal mode  Fast mode
-----------------------------------------------------------------
-- Fscl    100KHz    400KHz
-- Th_scl    4.0us    0.6us  High period of SCL
-- Tl_scl    4.7us    1.3us  Low period of SCL
-- Tsu:sta    4.7us    0.6us  setup time for a repeated start condition
-- Tsu:sto    4.0us    0.6us  setup time for a stop conditon
-- Tbuf    4.7us    1.3us  Bus free time between a stop and start condition
--

library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use ieee.numeric_std_unsigned.all;
library unisim;
    use unisim.vcomponents.all;

entity i2c_core is
    port (
        clk : in std_logic;
        nReset : in std_logic;

        clk_cnt : in unsigned(7 downto 0);

        cmd : in std_logic_vector(2 downto 0);
        cmd_ack : out std_logic;
        busy : out std_logic;

        Din : in std_logic;
        Dout : out std_logic;

        SCL_i : in std_logic;
        SDA_i : in std_logic;
        SCL_o : out std_logic;
        SDA_o : out std_logic
    );
end entity i2c_core;

architecture structural of i2c_core is
    --constant CMD_NOP  : std_logic_vector(2 downto 0) := "000";
    constant CMD_START  : std_logic_vector(2 downto 0) := "010";
    constant CMD_STOP  : std_logic_vector(2 downto 0) := "011";
    constant CMD_READ  : std_logic_vector(2 downto 0) := "100";
    constant CMD_WRITE  : std_logic_vector(2 downto 0) := "101";

    type cmds is (idle, start_a, start_b, start_c, start_d, stop_a, stop_b, stop_c, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d);
    signal state : cmds;

    signal SDAi, SCLi: std_logic;
    signal SDAo, SCLo: std_logic := '1';

    signal txd : std_logic;
    signal clk_en, slave_wait :std_logic;
    --IG  signal cnt : unsigned(7 downto 0) := clk_cnt; --IG: port can not act as initial value
    signal cnt : unsigned(7 downto 0); --IG: remove the initial value
    signal iscl, isda : std_logic;
begin
    -- whenever the slave is not ready it can delay the cycle by pulling SCL low
    slave_wait <= '1' when ((SCLo = '1') and (SCLi = '0')) else '0';

    SDAi  <= SDA_i;
    SCLi  <= SCL_i;
    SDA_o <= SDAo;
    SCL_o <= SCLo;

    -- generate clk enable signal
    gen_clken: process(clk, nReset)
    begin
        if (nReset = '0') then
            cnt <= (others => '0');
            clk_en <= '1'; --'0';
        elsif (clk'event and clk = '1') then
            if (cnt = 0) then
                clk_en <= '1';
                cnt <= clk_cnt;
            else
                if (slave_wait = '0') then
                    cnt <= cnt -1;
                end if;
                clk_en <= '0';
            end if;
        end if;
    end process gen_clken;

    -- generate statemachine
    nxt_state_decoder : process (clk, nReset, state, cmd, txd, Din)
        variable nxt_state : cmds;
        variable icmd_ack, ibusy, store_sda : std_logic;
        variable itxd : std_logic;
    begin

        nxt_state := state;

        icmd_ack := '0'; -- default no acknowledge
        ibusy := '1'; -- default busy

        store_sda := '0';

        itxd := txd;

        case (state) is
            -- idle
            when idle =>
                case cmd is
                    when CMD_START =>
                        nxt_state := start_a;
                        icmd_ack := '1'; -- command completed

                    when CMD_STOP =>
                        nxt_state := stop_a;
                        icmd_ack := '1'; -- command completed

                    when CMD_WRITE =>
                        nxt_state := wr_a;
                        icmd_ack := '1'; -- command completed
                        itxd := Din;

                    when CMD_READ =>
                        nxt_state := rd_a;
                        icmd_ack := '1'; -- command completed

                    when others =>
                        nxt_state := idle;
                        -- don't acknowledge NOP command            icmd_ack := '1'; -- command completed
                        ibusy := '0';
                end case;

            -- start
            when start_a =>
                nxt_state := start_b;

            when start_b =>
                nxt_state := start_c;

            when start_c =>
                nxt_state := start_d;

            when start_d =>
                nxt_state := idle;
                ibusy := '0'; -- not busy when idle


            -- stop
            when stop_a =>
                nxt_state := stop_b;

            when stop_b =>
                nxt_state := stop_c;

            when stop_c =>
                --        nxt_state := stop_d;

                --      when stop_d =>
                nxt_state := idle;
                ibusy := '0'; -- not busy when idle

            -- read
            when rd_a =>
                nxt_state := rd_b;

            when rd_b =>
                nxt_state := rd_c;

            when rd_c =>
                nxt_state := rd_d;
                store_sda := '1';

            when rd_d =>
                nxt_state := idle;
                ibusy := '0'; -- not busy when idle

            -- write
            when wr_a =>
                nxt_state := wr_b;

            when wr_b =>
                nxt_state := wr_c;

            when wr_c =>
                nxt_state := wr_d;

            when wr_d =>
                nxt_state := idle;
                ibusy := '0'; -- not busy when idle

        end case;

        -- generate regs
        if (nReset = '0') then
            state <= idle;
            cmd_ack <= '0';
            busy <= '0';
            txd <= '0';
            Dout <= '0';
        elsif (clk'event and clk = '1') then
            if (clk_en = '1') then
                state <= nxt_state;
                busy <= ibusy;

                txd <= itxd;
                if (store_sda = '1') then
                    Dout <= SDAi;
                end if;
            end if;

            cmd_ack <= icmd_ack and clk_en;
        end if;
    end process nxt_state_decoder;

    --
    -- convert states to SCL and SDA signals
    --
    output_decoder: process (state, SCLo, Din, SDAo)
    begin
        case (state) is
            when idle =>
                --iscl <= '1'; -- keep SCL high
                --isda <= '1'; -- keep SDA high
                iscl <= SCLo; -- keep SCL high
                isda <= SDAo; -- keep SDA high

            -- start
            when start_a =>
                iscl <= SCLo; -- keep SCL in same state (for repeated start)
                isda <= '1'; -- set SDA high

            when start_b =>
                iscl <= '1';  -- set SCL high
                isda <= '1'; -- keep SDA high

            when start_c =>
                iscl <= '1';  -- keep SCL high
                isda <= '0'; -- sel SDA low

            when start_d =>
                iscl <= '0'; -- set SCL low
                isda <= '0'; -- keep SDA low

            -- stop
            when stop_a =>
                iscl <= '0'; -- keep SCL disabled
                isda <= '0'; -- set SDA low

            when stop_b =>
                iscl <= '1'; -- set SCL high
                isda <= '0'; -- keep SDA low

            when stop_c =>
                iscl <= '1'; -- keep SCL high
                isda <= '1'; -- set SDA high

            -- write
            when wr_a =>
                iscl <= '0';  -- keep SCL low
                --        isda := txd; -- set SDA
                isda <= Din;

            when wr_b =>
                iscl <= '1';  -- set SCL high
                --        isda := txd; -- set SDA
                isda <= Din;

            when wr_c =>
                iscl <= '1';  -- keep SCL high
                --        isda := txd; -- set SDA
                isda <= Din;

            when wr_d =>
                iscl <= '0'; -- set SCL low
                --        isda := txd; -- set SDA
                isda <= Din;

            -- read
            when rd_a =>
                iscl <= '0'; -- keep SCL low
                isda <= '1'; -- tri-state SDA

            when rd_b =>
                iscl <= '1'; -- set SCL high
                isda <= '1'; -- tri-state SDA

            when rd_c =>
                iscl <= '1'; -- keep SCL high
                isda <= '1'; -- tri-state SDA

            when rd_d =>
                iscl <= '0'; -- set SCL low
                isda <= '1'; -- tri-state SDA
        end case;
    end process output_decoder;

    scl_sda_assignment: process(clk, nReset)
    begin

        -- generate registers
        if (nReset = '0') then
            SCLo <= '1';
            SDAo <= '1';
        elsif (clk'event and clk = '1') then
            if (clk_en = '1') then
                SCLo <= iscl;
                SDAo <= isda;
            end if;
        end if;
    end process scl_sda_assignment;


end architecture structural;




